﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NDiffStatLib.Utils;

namespace NDiffStatLib.DiffParsers
{
	/// <summary>
	/// Tranlated from Review Board (Python) branch master, changeset 839fc69 2012-09-06 20:26:59
	/// </summary>
	public class SvnDiffParser : DiffParser
	{
		private static Regex revision_re
		{
			get
			{
				return new Regex(
				   @"^(\(([^\)]+)\)\s)?"	// creating diffs between two branches of a remote repository 
											// will insert extra "relocation information" into the diff.
				 + @"(?:\d+-\d+-\d+\ +\d+:\d+:\d+\ +[A-Z]+\ +)?"
											// svnlook-style diffs contain a timestamp 
											// on each line before the
											// revision number.  This here is probably 
											// a really crappy way to
											// express that, but oh well.
				 + @"\ *\([Rr]ev(?:ision)?\ (\d+)\)$",
											// svnlook uses 'rev 0' while svn diff uses 'revision 0'

				 RegexOptions.Compiled | RegexOptions.Singleline);
			}
		}

		const string BINARY_STRING = "Cannot display: file marked as a binary type.";

		public SvnDiffParser( TextReader data, FileDiffFactory fileDiffFactory ) : base(data, fileDiffFactory) { }

		public SvnDiffParser( CustomTextReader reader, FileDiffFactory fileDiffFactory ) : base(reader, fileDiffFactory) { }

		protected override void parse_special_header( CustomTextReader reader, NameValueCollection info, StringBuilder sbHeader )
		{
			base.parse_special_header(reader, info, sbHeader);

			if (info.AllKeys.Contains("index") && reader.CurrentLine == BINARY_STRING) {

				// Skip this and the svn:mime-type line.
				sbHeader.AppendLine(reader.CurrentLine);
				reader.MoveFoward();
				sbHeader.AppendLine(reader.CurrentLine);
				reader.MoveFoward();

				info["binary"] = "True";
				info["origFile"] = info["index"];
				info["newFile"] = info["index"];

				// We can't get the revision info from this diff header.
				info["origInfo"] = "(unknown)";
				info["newInfo"] = "(working copy)";

			}
		}

		public static RevisionParseResult parse_diff_revision( string file_str, string revision_str )
		{
			// Some diffs have additional tabs between the parts of the file revisions
			revision_str = revision_str.Trim();

			// The "(revision )" is generated by IntelliJ and has the same meaning as "(working copy)". See bug 1937.
			if (revision_str.In("(working copy)", "(revision )")) {
				return new RevisionParseResult(file_str, Revision.HEAD);
			}

			// Binary diffs don't provide revision information, so we set a fake "(unknown)"
			// in the SVNDiffParser. This will never actually appear in SVN diffs.
			if (revision_str == "(unknown)") {
				return new RevisionParseResult(file_str, Revision.UNKNOWN);
			}

			Match match = revision_re.Match(revision_str);
			if (!match.Success) {
				throw new SvnDiffParserError("Unable to parse diff revision header '{0}'", revision_str);
			}

			string relocated_file = match.Groups[2].Value;
			string revision = match.Groups[3].Value;

			if (revision == "0") {
				revision = Revision.PRE_CREATION;
			}

			if (!relocated_file.IsNormalized()) {
				if (!relocated_file.StartsWith("...")) {
					throw new SvnDiffParserError("Unable to parse SVN relocated path '{0}'", relocated_file);
				}
				file_str = string.Format("{0}/{1}", relocated_file.Substring(4), file_str);
			}

			return new RevisionParseResult(file_str, revision);
		}
	}

	public class RevisionParseResult
	{
		public string file_str;
		public Revision revision;
		public RevisionParseResult( string file_str, string rev_str )
		{
			this.file_str = file_str;
			this.revision = new Revision(rev_str);
		}
	}

	public class SvnDiffParserError : DiffParserError
	{
		public SvnDiffParserError( string msg, params string[] args ) : base(msg, args) { }
	}
}
